package com.supreme.threadserver.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.supreme.commonboot.exception.BizException;
import com.supreme.commonboot.utils.GlobalSpringUtils;
import com.supreme.commonentity.mysql.primary.BizOrder;
import com.supreme.commonentity.mysql.primary.MBTest;
import com.supreme.commonmybatis.entity.primary.MBStorage;
import com.supreme.commonmybatis.mapper.OrderMapper;
import com.supreme.commonmybatis.mapper.StorageMapper;
import com.supreme.commonmybatis.mapper.TestMapper;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class TransactionService {

    @Resource
    private TestMapper testMapper;

    @Resource
    private StorageMapper storageMapper;

    @Resource
    private AsyncThreadService asyncThreadService;

    @Resource
    private TransactionTemplate transactionTemplate;

    public List<MBTest> testList() {
        return this.testMapper.selectList(Wrappers.emptyWrapper());
    }

    @Transactional
    public int createTest(MBTest test) {
        final int insert = testMapper.insert(test);
        if (test.getFirstName().equalsIgnoreCase("error")) {
            throw new RuntimeException("first name can not contain error");
        }
        return insert;
    }

    @Transactional
    public int createStorage(MBStorage storage) {
        final int insert = storageMapper.insert(storage);
        return insert;
    }

    @Transactional(rollbackFor = Exception.class)
    public int createTestStorage(MBStorage storage) throws Exception {
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(storage.getVarcharLabel());
        mbTest.setLastName(storage.getCharLabel());
        int testInsert = testMapper.insert(mbTest);
        System.out.println("testInsert: " + testInsert);

        int storageInsert = storageMapper.insert(storage);
        System.out.println("storageInsert: " + storageInsert);

        if (storage.getTinyInt() == 4) {
            throw new RuntimeException("tinyInt can not equal 4");
        }

        if (storage.getTinyInt() == 14) {
            throw new BizException("tinyInt can not equal 14");
        }

        if (storage.getTinyInt() == 24) {
            throw new Exception("tinyInt can not equal 24");
        }

        return storageInsert;
    }

    @Transactional
    public int privateTestStorage(MBStorage storage) {
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(storage.getVarcharLabel());
        mbTest.setLastName(storage.getCharLabel());
        int testInsert = testMapper.insert(mbTest);
        System.out.println("testInsert: " + testInsert);

        int storageInsert = storageMapper.insert(storage);
        System.out.println("storageInsert: " + storageInsert);

        return storageInsert;
    }

    public int createPrivateTestStorage(MBStorage storage) {
        return privateTestStorage(storage);
    }

    @Transactional(rollbackFor = Exception.class)
    public int createThreadTestStorage(MBStorage storage) {
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(storage.getVarcharLabel());
        mbTest.setLastName(storage.getCharLabel());
        int testInsert = testMapper.insert(mbTest);
        System.out.println("testInsert: " + testInsert);

        final Thread thread = new Thread(() -> storageMapper.insert(storage));
        thread.start();

        return 0;
    }

    @Async
    public void asyncCreateStorage(MBStorage storage) {
        final int result = storageMapper.insert(storage);
        System.out.println("asyncCreateStorage: " + result);
    }

    @Async
    public Future<Integer> asyncFutureCreateStorage(MBStorage storage) {
        int storageInsert = storageMapper.insert(storage);
        return new AsyncResult<>(storageInsert);
    }

    public int threadCreateStorage(MBStorage storage) {
        AtomicInteger atomicInteger = new AtomicInteger();
        final Thread thread = new Thread(() -> {
            int storageInsert = storageMapper.insert(storage);
            atomicInteger.set(storageInsert);
        });
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return atomicInteger.get();
    }

    public int callableCreateStorage(MBStorage storage) {
        final Callable<Integer> integerCallable = () -> storageMapper.insert(storage);
        final FutureTask<Integer> integerFutureTask = new FutureTask<>(integerCallable);
        final Thread thread = new Thread(integerFutureTask);
        thread.start();
        Integer integer = 0;
        try {
            integer = integerFutureTask.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return integer;
    }

    @Transactional(rollbackFor = Exception.class)
    public int createCallableTestStorage(MBStorage storage) {
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(storage.getVarcharLabel());
        mbTest.setLastName(storage.getCharLabel());
        int testInsert = testMapper.insert(mbTest);
        System.out.println("testInsert: " + testInsert);

        int storageInsert = callableCreateStorage(storage);
        System.out.println("storageInsert: " + storageInsert);

        return storageInsert;
    }

    @Transactional(rollbackFor = Exception.class)
    public int createAsyncTestStorage(MBStorage storage) {
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(storage.getVarcharLabel());
        mbTest.setLastName(storage.getCharLabel());
        int testInsert = testMapper.insert(mbTest);
        System.out.println("testInsert: " + testInsert);

        asyncCreateStorage(storage);

        return 0;
    }

    @Transactional(rollbackFor = Exception.class)
    public int createAsyncFutureTestStorage(MBStorage storage) {
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(storage.getVarcharLabel());
        mbTest.setLastName(storage.getCharLabel());
        int testInsert = testMapper.insert(mbTest);
        System.out.println("testInsert: " + testInsert);

        final Future<Integer> integerFuture = asyncFutureCreateStorage(storage);
        Integer futureResult = 0;
        try {
            futureResult = integerFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("storageInsert: " + futureResult);

        return futureResult;
    }

    @Transactional(rollbackFor = Exception.class)
    public int createRandomOrder() {
        String orderCode = RandomStringUtils.randomNumeric(10);
        int orderType = RandomUtils.nextInt(1, 10);
        int orderNum = RandomUtils.nextInt(1, 100);
        long userId = RandomUtils.nextLong(1, 1000);
        long goodsId = RandomUtils.nextLong(1, 1000);
        BizOrder bizOrder = new BizOrder().setOrderCode(orderCode).setOrderType(orderType).setOrderNum(orderNum).setUserId(userId).setGoodsId(goodsId);
        int insert = GlobalSpringUtils.getBean(OrderMapper.class).insert(bizOrder);
        int update = GlobalSpringUtils.getBean(OrderMapper.class).update(new BizOrder().setOrderNum(orderNum + 10), Wrappers.lambdaUpdate(BizOrder.class).eq(BizOrder::getOrderCode, orderCode));

        Future<BizOrder> bizOrderFuture = asyncThreadService.updateOrderNum(orderCode, null);
        try {
            bizOrderFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }

        return insert;
    }

    @Transactional(rollbackFor = Exception.class)
    public void tatSyncTest(int status) {
        boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
        System.out.println("actualTransactionActive = " + actualTransactionActive);
        boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        System.out.println("synchronizationActive = " + synchronizationActive);
        boolean currentTransactionReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        System.out.println("currentTransactionReadOnly = " + currentTransactionReadOnly);

        if (actualTransactionActive) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void beforeCommit(boolean readOnly) {
                    System.out.println("beforeCommit = " + readOnly);
                }

                @Override
                public void beforeCompletion() {
                    System.out.println("beforeCompletion");
                }

                @Override
                public void afterCommit() {
                    System.out.println("afterCommit");
                }

                @Override
                public void afterCompletion(int status) {
                    System.out.println("afterCompletion = " + status);
                }
            });
        }

        String firstName = RandomStringUtils.randomAlphabetic(8);
        String lastName = RandomStringUtils.randomAlphabetic(8);
        MBTest mbTest = new MBTest();
        mbTest.setFirstName(firstName);
        mbTest.setLastName(lastName);
        testMapper.insert(mbTest);

        if (status < 0) {
            throw new BizException("fuck");
        }

        System.out.println("finish");
    }

    public void tatTemplateTest() {
        MBTest execute = transactionTemplate.execute(status -> {
            String firstName = RandomStringUtils.randomAlphabetic(8);
            String lastName = RandomStringUtils.randomAlphabetic(8);
            MBTest mbTest = new MBTest();
            mbTest.setFirstName(firstName);
            mbTest.setLastName(lastName);
            testMapper.insert(mbTest);
            return mbTest;
        });
    }
}
